﻿// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================

using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Xml.Linq;

namespace SL.IdentityModel.Protocols.WSTrust
{
	/// <summary>
	/// Class serializes XML from the response message in to the RequestSecurityTokenRespnse
	/// </summary>
	internal abstract class WSTrustResponseSerializer
	{
		private const string Address = "Address";
		private const string AppliesTo = "AppliesTo";
		private const string Created = "Created";
		private const string EndpointReference = "EndpointReference";
		private const string Expires = "Expires";
		private const string Lifetime = "Lifetime";
		private XNamespace nsAddressing = "http://www.w3.org/2005/08/addressing";
		private XNamespace nsSecurityPolicy = "http://schemas.xmlsoap.org/ws/2004/09/policy";
		private XNamespace nsUtility = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

		protected XNamespace NamespaceURI
		{
			get { return this.namespaceUri; }
			set { this.namespaceUri = value; }
		}
		private XNamespace namespaceUri;

		/// <summary>
		/// Create as instance of the class by reading the response message
		/// </summary>
		/// <param name="messageResponse"></param>
		/// <returns></returns>
		public RequestSecurityTokenResponse ReadXml(Message messageResponse)
		{
			if (null == messageResponse)
			{
				throw new ArgumentNullException("messageResponse is null");
			}

			XDocument rstr = XDocument.Parse(messageResponse.ToString());
			RequestSecurityTokenResponse response = new RequestSecurityTokenResponse();

			response.RequestedSecurityToken = ReadRequestedSecurityToken(rstr);
			response.AppliesTo = ReadAppliesTo(rstr);
			response.Created = ReadLifeTimeHelper(rstr, Created);
			response.Expires = ReadLifeTimeHelper(rstr, Expires);
			response.ProofTokenReference = ReadProofTokenReference(rstr);
			response.EntropyBytes = ReadEntropy(rstr);
			response.KeySizeInBits = ReadKeySize(rstr);
			response.KeyType = ReadKeyType(rstr);
			response.ComputedKeyAlgorithm = ReadComputedKeyAlgorithm(rstr);
			response.ProtectedKeyBytes = ReadProtectedKey(rstr);

			return response;
		}

		/// <summary>
		/// Reads AppliesTo element from the response
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>AppliesTo EndpointAddress</returns>
		private EndpointAddress ReadAppliesTo(XDocument rstr)
		{
			string appliesTo = (from r in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
									.Elements(nsSecurityPolicy + AppliesTo)
									.Elements(nsAddressing + EndpointReference)
									.Elements(nsAddressing + Address)
								select r.Value).FirstOrDefault<string>();

			if (null == appliesTo)
			{
				return null;
			}

			return new EndpointAddress(appliesTo);
		}

		/// <summary>
		/// Reads ComputedKey element from the response
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>ComputedKey string</returns>
		private string ReadComputedKeyAlgorithm(XDocument rstr)
		{
			string computedKey = (from r in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
										.Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestedProofToken)
										.Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.ComputedKey)
								  select r.Value).FirstOrDefault<string>();

			if (string.IsNullOrEmpty(computedKey))
			{
				return null;
			}

			return computedKey;
		}

		/// <summary>
		/// Reads BinarySecret element from the response
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>ServerEntropy byte[]</returns>
		private byte[] ReadEntropy(XDocument rstr)
		{
			string entropyString = (from r in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
										.Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.Entropy)
										.Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.BinarySecret)
									select r.Value).FirstOrDefault<string>();

			if (String.IsNullOrEmpty(entropyString))
			{
				return null;
			}

			return Convert.FromBase64String(entropyString);
		}

		/// <summary>
		/// Reads KeySize element from the response
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>KeySize int?</returns>
		private int? ReadKeySize(XDocument rstr)
		{
			string keySizeString = (from r in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
										.Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.KeySize)
									select r.Value).FirstOrDefault<string>();

			if (String.IsNullOrEmpty(keySizeString))
			{
				return null;
			}

			return Convert.ToInt32(keySizeString);
		}

		/// <summary>
		/// Reads KeyType element from the response
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>KeyType string</returns>
		private string ReadKeyType(XDocument rstr)
		{
			string keyType = (from r in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
										.Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.KeyType)
							  select r.Value).FirstOrDefault<string>();

			return keyType;
		}

		/// <summary>
		/// Reads LifeTime element from the RequestSecurityTokenResponse XML
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <param name="elementName">Created or Expires, specifies element being read</param>
		/// <returns></returns>
		private DateTime ReadLifeTimeHelper(XDocument rstr, string elementName)
		{
			DateTime tempDateTime;
			string time;

			time = (from r in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
								 .Elements(this.NamespaceURI + Lifetime)
								 .Elements(nsUtility + elementName)
					select r.Value).FirstOrDefault<string>() ?? String.Empty;

			if (DateTime.TryParse(time, out tempDateTime))
			{
				return tempDateTime;
			}
			else
			{
				throw new ArgumentException(string.Format("bad datetime value for {0}", elementName));
			}
		}

		/// <summary>
		/// Reads requested security token
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>RequestedSecurityToken</returns>
		private RequestedSecurityToken ReadRequestedSecurityToken(XDocument rstr)
		{
			XElement requestedToken = (from rt in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestedSecurityToken)
									   select rt).FirstOrDefault<XElement>();

			if (null == requestedToken)
			{
				throw new ArgumentNullException("No RequestedSecurityToken in RequestSecurityTokenResponse");
			}

			XNode token = requestedToken.DescendantNodes().FirstOrDefault();

			if (null == token)
			{
				throw new ArgumentNullException("RequestedSecurityToken did not contain a token");
			}

			return new RequestedSecurityToken(token.ToString(SaveOptions.DisableFormatting));
		}

		/// <summary>
		/// Reads SecurityTokenReference element from the response
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>SecurityTokenReference XNode</returns>
		private XNode ReadProofTokenReference(XDocument rstr)
		{
			XElement attachedReference = (from rt in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
										  .Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestedAttachedReference)
										  select rt).FirstOrDefault<XElement>();

			if (null == attachedReference)
			{
				return null;
			}

			return attachedReference.DescendantNodes().FirstOrDefault();
		}

		/// <summary>
		/// Reads BinarySecret element from inside the RequestedProofToken node in the response
		/// </summary>
		/// <param name="rstr">RequestSecurityTokenResponse message XML</param>
		/// <returns>ProtectedKey byte[]</returns>
		private byte[] ReadProtectedKey(XDocument rstr)
		{
			string protectedKeyString = (from r in rstr.Descendants(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestSecurityTokenResponse)
											 .Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.RequestedProofToken)
											 .Elements(this.NamespaceURI + WSTrust13Constants.ElementNames.BinarySecret)
										 select r.Value).FirstOrDefault<string>();

			if (string.IsNullOrEmpty(protectedKeyString))
			{
				return null;
			}

			return Convert.FromBase64String(protectedKeyString);
		}


	}



	/// <summary>
	/// Class serializes XML from the response message in to the RequestSecurityTokenRespnse
	/// </summary>
	internal class WSTrust13ResponseSerializer
		: WSTrustResponseSerializer
	{
		internal WSTrust13ResponseSerializer()
			: base()
		{
			this.NamespaceURI = WSTrust13Constants.NamespaceURI;
		}
	}

	/// <summary>
	/// Class serializes XML from the response message in to the RequestSecurityTokenRespnse
	/// </summary>
	internal class WSTrust2005ResponseSerializer
		: WSTrustResponseSerializer
	{
		internal WSTrust2005ResponseSerializer()
			: base()
		{
			this.NamespaceURI = "http://schemas.xmlsoap.org/ws/2005/02/trust";
		}
	}
}
